#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <assert.h>

#include "debug/debug.h"
#include "media_message.h"
#include "media.h"

struct video_message* video_message_alloc(void)
{
	struct video_message* video_message = malloc(sizeof(struct video_message));
	if (video_message == NULL)
	{
		print_error("video frame message alloc failed.");
		return video_message;
	}

	bzero(video_message, sizeof(*video_message));
	pthread_spin_init(&video_message->spin, 0);
	
	return video_message;
}

void video_message_free(struct video_message* message)
{
	if (message == NULL)
		return;

	assert(!message->ref_cnt);

	free(message);
}

void video_message_get(struct video_message* message)
{
	pthread_spin_lock(&message->spin);
	message->ref_cnt++;
	pthread_spin_unlock(&message->spin);

}

void video_message_put(struct video_message* message)
{
	if (!message)
		return;

	pthread_spin_lock(&message->spin);
	message->ref_cnt--;
	pthread_spin_unlock(&message->spin);

	assert(message->ref_cnt >= 0);

	if (!message->ref_cnt)
	{
		pthread_spin_destroy(&message->spin);
		if (message->put_message)
			message->put_message(message->handle, message);
		else
		{
			video_message_free(message);
		}
	}
}

struct message_context* video_message_context_alloc(void)
{
	struct message_context* context = malloc(sizeof(struct message_context));
	if (context == NULL)
	{
		print_error("video frame message alloc failed.");
		return context;
	}

	bzero(context, sizeof(*context));
	pthread_spin_init(&context->spin, 0);

	return context;

}

void video_message_context_free(struct message_context* context)
{
	if (context == NULL)
		return;

	assert(!context->ref_cnt);

	free(context);
}

void video_message_context_get(struct message_context* context)
{
	pthread_spin_lock(&context->spin);
	context->ref_cnt++;
	pthread_spin_unlock(&context->spin);

}

void video_message_context_data_put(struct message_context* context)
{
	if (context->type == MESSAGE_TYPE_VIDEO_FRAME)
	{
		struct video_frame* frame = context->data.frame;
		assert(frame);
		video_frame_put(frame);
	}
	else if (context->type == MESSAGE_TYPE_CMD)
	{
		struct video_cmd* cmd = context->data.cmd;
		assert(cmd);
		video_cmd_put(cmd);
	}
	else if (context->type == MESSAGE_TYPE_MEDIA_PKT)
	{
		struct media_packet* pkt = context->data.pkt;
		assert(pkt);
		media_packet_put(pkt);
	}
	else if(context->type != MESSAGE_TYPE_NOTIFY)
	{
		print_warning("unknow message data type.");
	}
}

void video_message_context_put_all(struct message_context* context)
{
	if (!context)
		return;
	pthread_spin_lock(&context->spin);
	context->ref_cnt--;
	pthread_spin_unlock(&context->spin);

	assert(context->ref_cnt >= 0);

	if (!context->ref_cnt)
	{
		pthread_spin_destroy(&context->spin);
		video_message_context_data_put(context);

		if (context->put_context)
			context->put_context(context->handle, context);
		else
		{
			video_message_context_free(context);
		}
	}
}

void video_message_context_put(struct message_context* context)
{
	if (!context)
		return;

	pthread_spin_lock(&context->spin);
	context->ref_cnt--;
	pthread_spin_unlock(&context->spin);

	assert(context->ref_cnt >= 0);

	if (!context->ref_cnt)
	{
		pthread_spin_destroy(&context->spin);
		if (context->put_context)
			context->put_context(context->handle, context);
		else
		{
			video_message_context_free(context);
		}
	}
}

struct message_queue*  video_message_queue_open(void)
{
	struct message_queue* queue = message_queue_create(0, 0);

	return queue;
}

void video_message_queue_close(struct message_queue* queue)
{
	assert(queue);
	message_queue_delete(queue);
}


int video_message_queue_size(struct message_queue* queue)
{
	return message_queue_size(queue);
}

bool video_message_queue_is_empty(struct message_queue* queue)
{
	return message_queue_is_empty(queue);
}


int video_message_queue_send(struct message_queue* queue, struct message_context* ctx)
{
	struct video_message* message = NULL;

	assert(queue);
	assert(ctx);

	message = video_message_alloc();
	assert(message);
	message->ctx = ctx;
	video_message_get(message);
	message_queue_send(queue, (struct message*)message);

	return 0;
}

struct message_context* video_message_queue_receive(struct message_queue* queue)
{
	struct message_context* ctx = NULL;

	struct video_message* message = (struct video_message*)message_queue_receive(queue);

	assert(message);
	assert(message->ctx);

	ctx = message->ctx;
	video_message_put(message);

	return ctx;
}

int media_frame_message_send(struct message_queue* queue, struct video_frame* frame, int16_t angle)
{
	assert(queue);
	struct message_context* ctx = video_message_context_alloc();
	assert(ctx);

	ctx->type = MESSAGE_TYPE_VIDEO_FRAME;
	ctx->data.frame = frame;
	ctx->angle = angle;

	video_message_context_get(ctx);
	video_message_queue_send(queue, ctx);

	return 0;
}

int media_stream_message_send(struct message_queue* queue, struct media_stream* stream)
{
	assert(queue);
	struct message_context* ctx = video_message_context_alloc();
	assert(ctx);

	ctx->type = MESSAGE_TYPE_MEDIA_STREAM;
	ctx->data.stream = stream;

	video_message_context_get(ctx);
	video_message_queue_send(queue, ctx);

	return 0;
}

int media_notify_message_send(struct message_queue* queue)
{
	if(queue == NULL)
		return -1;
	assert(queue);
	struct message_context* ctx = video_message_context_alloc();
	assert(ctx);

	ctx->type = MESSAGE_TYPE_NOTIFY;

	video_message_context_get(ctx);
	video_message_queue_send(queue, ctx);

	return 0;
}

void media_video_cmd_message(struct message_queue *queue, struct video_cmd *cmd)
{
	assert(queue);

	struct message_context* ctx = video_message_context_alloc();
	assert(ctx);

	ctx->type = MESSAGE_TYPE_CMD;
	ctx->data.cmd = cmd;

	video_message_context_get(ctx);

	video_message_queue_send(queue, ctx);
}


int media_send_action_cmd(enum video_action action, enum action_cmd action_cmd, uint8_t* data, size_t size)
{
	struct video_cmd* cmd = NULL;
	struct message_queue* queue = get_action_message_queue(action);
	assert(queue);

	int ret = -1;

	cmd = video_cmd_alloc();
	assert(cmd);
	video_cmd_get(cmd);

	cmd->action = action;
	cmd->action_cmd = action_cmd;

	if (size >= cmd->data_total_size)
	{
		video_cmd_put(cmd);
		return ret;
	}

	if (size > 0)
	{
		assert(data);
		memcpy(cmd->data, data, size);
		cmd->size = size;
	}

	media_video_cmd_message(queue, cmd);

	return 0;

}